/********************************************************************
	created:	30:9:2007   19:23
	author:		tiamo
	purpose:	device io control
*********************************************************************/

#include "stdafx.h"

// internal io control
NTSTATUS AppleKeyboardInternalIoControl(__in PDEVICE_OBJECT pDeviceObject,__in PIRP pIrp)
{
	NTSTATUS statusRet = STATUS_SUCCESS;
	CDeviceExtension* pDevExt = static_cast<CDeviceExtension*>(pDeviceObject->DeviceExtension);
	PIO_STACK_LOCATION pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
	BOOLEAN bReleaseRemoveLock = FALSE;
	BOOLEAN bCompleteRequest = TRUE;

	AppleDebugPrint(DBGLEVEL_INFO,"Enter AppleKeyboardInternalIoControl %p,%p\n",pDeviceObject,pIrp);

	__try
	{
		statusRet = IoAcquireRemoveLock(&pDevExt->m_removeLock,pIrp);
		if(!NT_SUCCESS(statusRet))
			__leave;

		bReleaseRemoveLock = TRUE;
		ASSERT(pDevExt->m_bDeviceStarted || IOCTL_INTERNAL_KEYBOARD_CONNECT == pIrpSp->Parameters.DeviceIoControl.IoControlCode);

		switch(pIrpSp->Parameters.DeviceIoControl.IoControlCode)
		{
		case IOCTL_KEYBOARD_QUERY_INDICATOR_TRANSLATION:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard query indicator translation\n");
				AppleDbgBreakPoint();
				if(pIrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(KEYBOARD_INDICATOR_TRANSLATION) + 2 * sizeof(INDICATOR_LIST))
				{
					statusRet = STATUS_BUFFER_TOO_SMALL;
					__leave;
				}

				PKEYBOARD_INDICATOR_TRANSLATION pInfo = static_cast<PKEYBOARD_INDICATOR_TRANSLATION>(pIrp->AssociatedIrp.SystemBuffer);
				pInfo->NumberOfIndicatorKeys = 3;
				pInfo->IndicatorList[0].MakeCode = 0x3a;
				pInfo->IndicatorList[0].IndicatorFlags = KEYBOARD_CAPS_LOCK_ON;
				pInfo->IndicatorList[1].MakeCode = 0x45;
				pInfo->IndicatorList[1].IndicatorFlags = KEYBOARD_NUM_LOCK_ON;
				pInfo->IndicatorList[2].MakeCode = 0x46;
				pInfo->IndicatorList[2].IndicatorFlags = KEYBOARD_SCROLL_LOCK_ON;
				pIrp->IoStatus.Information = sizeof(KEYBOARD_INDICATOR_TRANSLATION) + 2 * sizeof(INDICATOR_LIST);
			}
			break;

		case IOCTL_KEYBOARD_QUERY_ATTRIBUTES:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard query attribute\n");
	
				if(pIrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(KEYBOARD_ATTRIBUTES))
				{
					statusRet = STATUS_BUFFER_TOO_SMALL;
					__leave;
				}

				RtlCopyMemory(pIrp->AssociatedIrp.SystemBuffer,&pDevExt->m_attributeKeyboard,sizeof(KEYBOARD_ATTRIBUTES));
				pIrp->IoStatus.Information = sizeof(KEYBOARD_ATTRIBUTES);
			}
			break;

		case IOCTL_KEYBOARD_SET_TYPEMATIC:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard set typematic\n");

				if(pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(KEYBOARD_TYPEMATIC_PARAMETERS))
				{
					statusRet = STATUS_INVALID_PARAMETER;
					__leave;
				}

				PKEYBOARD_TYPEMATIC_PARAMETERS pParam = static_cast<PKEYBOARD_TYPEMATIC_PARAMETERS>(pIrp->AssociatedIrp.SystemBuffer);
				if(pParam->Rate || pParam->Delay)
				{
					if( pParam->Rate < pDevExt->m_attributeKeyboard.KeyRepeatMinimum.Rate || 
						pParam->Rate > pDevExt->m_attributeKeyboard.KeyRepeatMaximum.Rate ||
						pParam->Delay < pDevExt->m_attributeKeyboard.KeyRepeatMinimum.Delay ||
						pParam->Delay > pDevExt->m_attributeKeyboard.KeyRepeatMaximum.Delay)
					{
						statusRet = STATUS_INVALID_PARAMETER;
						__leave;
					}

					RtlCopyMemory(&pDevExt->m_paramTypematic,pParam,sizeof(KEYBOARD_TYPEMATIC_PARAMETERS));
					pDevExt->m_lRepeatPeriod = 1000 / pParam->Rate;
					pDevExt->m_llTimeRepeatDelay.QuadPart = -pParam->Delay * 10000;

					AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard repeat period %d,repeat delay%d\n",pDevExt->m_lRepeatPeriod,pParam->Delay*10000);
				}
			}
			break;

		case IOCTL_KEYBOARD_SET_INDICATORS:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard set indicator\n");

				if(pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(KEYBOARD_INDICATOR_PARAMETERS))
				{
					statusRet = STATUS_INVALID_PARAMETER;
					__leave;
				}

				PKEYBOARD_INDICATOR_PARAMETERS pParam = static_cast<PKEYBOARD_INDICATOR_PARAMETERS>(pIrp->AssociatedIrp.SystemBuffer);
				statusRet = AppleKeyboardSetLedIndicators(pDevExt,pParam,pIrp,pIrpSp);
				bCompleteRequest = FALSE;
			}
			break;

		case IOCTL_KEYBOARD_QUERY_TYPEMATIC:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard query typematic\n");

				if(pIrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(KEYBOARD_TYPEMATIC_PARAMETERS))
				{
					statusRet = STATUS_BUFFER_TOO_SMALL;
					__leave;
				}

				RtlCopyMemory(pIrp->AssociatedIrp.SystemBuffer,&pDevExt->m_paramTypematic,sizeof(KEYBOARD_TYPEMATIC_PARAMETERS));
				pIrp->IoStatus.Information = sizeof(KEYBOARD_TYPEMATIC_PARAMETERS);
			}
			break;

		case IOCTL_KEYBOARD_QUERY_INDICATORS:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard query indicator\n");

				if(pIrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(KEYBOARD_INDICATOR_PARAMETERS))
				{
					statusRet = STATUS_BUFFER_TOO_SMALL;
					__leave;
				}

				RtlCopyMemory(pIrp->AssociatedIrp.SystemBuffer,&pDevExt->m_paramIndicator,sizeof(KEYBOARD_INDICATOR_PARAMETERS));
				pIrp->IoStatus.Information = sizeof(KEYBOARD_INDICATOR_PARAMETERS);
			}
			break;

		case IOCTL_INTERNAL_KEYBOARD_CONNECT:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard connect\n");
				if(pDevExt->m_dataConnect.ClassService)
				{
					AppleDebugPrint(DBGLEVEL_ERROR,"ERROR: AppleKeyboard multi connect\n");
					AppleDbgBreakPoint();
					statusRet = STATUS_SHARING_VIOLATION;
					__leave;
				}

				if(pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(CONNECT_DATA))
				{
					statusRet = STATUS_INVALID_PARAMETER;
					__leave;
				}

				RtlCopyMemory(&pDevExt->m_dataConnect,pIrpSp->Parameters.DeviceIoControl.Type3InputBuffer,sizeof(CONNECT_DATA));
			}
			break;

		case IOCTL_INTERNAL_KEYBOARD_DISCONNECT:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard disconnect\n");
				AppleDbgBreakPoint();
				statusRet = STATUS_NOT_IMPLEMENTED;
			}
			break;

		case IOCTL_INTERNAL_KEYBOARD_ENABLE:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard enable\n");
				statusRet = STATUS_NOT_SUPPORTED;
			}
			break;

		case IOCTL_INTERNAL_KEYBOARD_DISABLE:
			{
				AppleDebugPrint(DBGLEVEL_INFO,"AppleKeyboard disable\n");
				statusRet = STATUS_NOT_SUPPORTED;
			}
			break;

		default:
			AppleDebugPrint(DBGLEVEL_ERROR,"ERROR: AppleKeyboard unknown iocontrolcode\n");
			AppleDbgBreakPoint();
			statusRet = STATUS_INVALID_DEVICE_REQUEST;
			break;
		}
	}
	__finally
	{
		if(bCompleteRequest)
		{
			pIrp->IoStatus.Status = statusRet;
			IoCompleteRequest(pIrp,IO_NO_INCREMENT);
		}

		if(bReleaseRemoveLock)
			IoReleaseRemoveLock(&pDevExt->m_removeLock,pIrp);
	}

	AppleDebugPrint(DBGLEVEL_INFO,"Leave AppleKeyboardInternalIoControl 0x%08x\n",statusRet);

	return statusRet;
}

// device io control
NTSTATUS AppleKeyboardIoControl(__in PDEVICE_OBJECT pDeviceObject,__in PIRP pIrp)
{
	NTSTATUS statusRet = STATUS_SUCCESS;
	CDeviceExtension* pDevExt = static_cast<CDeviceExtension*>(pDeviceObject->DeviceExtension);

	AppleDebugPrint(DBGLEVEL_INFO,"Enter AppleKeyboardIoControl %p,%p\n",pDeviceObject,pIrp);

	statusRet = IoAcquireRemoveLock(&pDevExt->m_removeLock,pIrp);
	if(NT_SUCCESS(statusRet))
	{
		IoSkipCurrentIrpStackLocation(pIrp);
		statusRet = IoCallDriver(pDevExt->m_pLowerDevice,pIrp);
		IoReleaseRemoveLock(&pDevExt->m_removeLock,pIrp);
	}
	else
	{
		pIrp->IoStatus.Status = statusRet;
		IoCompleteRequest(pIrp,IO_NO_INCREMENT);
	}
	
	AppleDebugPrint(DBGLEVEL_INFO,"Leave AppleKeyboardIoControl 0x%08x\n",statusRet);

	return statusRet;
}

// set led indicator
NTSTATUS AppleKeyboardSetLedIndicators(__in CDeviceExtension* pDevExt,__in PKEYBOARD_INDICATOR_PARAMETERS pParam,__in PIRP pIrp,
									   __in PIO_STACK_LOCATION pIrpSp)
{
	NTSTATUS statusRet = STATUS_SUCCESS;
	PVOID pBuffer = 0;
	PMDL pMdl = 0;
	BOOLEAN bReleaseRemoveLock = FALSE;
	BOOLEAN bCompleteRequest = TRUE;

	__try
	{
		statusRet = IoAcquireRemoveLock(&pDevExt->m_removeLock,pIrp);
		if(!NT_SUCCESS(statusRet))
			__leave;

		bReleaseRemoveLock = TRUE;
		if(!pDevExt->m_pHidData->m_hidCaps.OutputReportByteLength)
			__leave;

		pBuffer = ExAllocatePoolWithTag(NonPagedPool,pDevExt->m_pHidData->m_hidCaps.OutputReportByteLength,'Appl');
		if(!pBuffer)
		{
			statusRet = STATUS_INSUFFICIENT_RESOURCES;
			__leave;
		}

		pMdl = IoAllocateMdl(pBuffer,pDevExt->m_pHidData->m_hidCaps.OutputReportByteLength,FALSE,FALSE,0);
		if(!pMdl)
		{
			statusRet = STATUS_INSUFFICIENT_RESOURCES;
			__leave;
		}

		bCompleteRequest = bReleaseRemoveLock = FALSE;
		MmBuildMdlForNonPagedPool(pMdl);
		RtlZeroMemory(pBuffer,pDevExt->m_pHidData->m_hidCaps.OutputReportByteLength);

		ULONG index = 0;
		USAGE usageList[5] = {0};

		if(pParam->LedFlags & KEYBOARD_CAPS_LOCK_ON)
			usageList[index++] = HID_USAGE_LED_CAPS_LOCK;

		if(pParam->LedFlags & KEYBOARD_NUM_LOCK_ON)
			usageList[index++] = HID_USAGE_LED_NUM_LOCK;

		if(pParam->LedFlags & KEYBOARD_SCROLL_LOCK_ON)
			usageList[index++] = HID_USAGE_LED_SCROLL_LOCK;

		if(pParam->LedFlags & KEYBOARD_KANA_LOCK_ON)
			usageList[index++] = HID_USAGE_LED_KANA;

		if(!index)
		{
			usageList[0] = HID_USAGE_LED_SCROLL_LOCK;
			index = 1;
			HidP_UnsetUsages(HidP_Output,HID_USAGE_PAGE_LED,HIDP_LINK_COLLECTION_UNSPECIFIED,usageList,&index,pDevExt->m_pHidData->m_pPreparsedData,
							 static_cast<PCHAR>(pBuffer),pDevExt->m_pHidData->m_hidCaps.OutputReportByteLength);
		}
		else
		{
			HidP_SetUsages(HidP_Output,HID_USAGE_PAGE_LED,HIDP_LINK_COLLECTION_UNSPECIFIED,usageList,&index,pDevExt->m_pHidData->m_pPreparsedData,
						   static_cast<PCHAR>(pBuffer),pDevExt->m_pHidData->m_hidCaps.OutputReportByteLength);
		}

		pIrpSp->Parameters.Others.Argument4 = pIrp->MdlAddress;
		pIrp->MdlAddress = pMdl;
		IoCopyCurrentIrpStackLocationToNext(pIrp);

		pIrpSp = IoGetNextIrpStackLocation(pIrp);
		pIrpSp->MajorFunction = IRP_MJ_WRITE;
		pIrpSp->Parameters.Write.Length = pDevExt->m_pHidData->m_hidCaps.OutputReportByteLength;
		pIrpSp->Parameters.Write.Key = 0;
		pIrpSp->Parameters.Write.ByteOffset.QuadPart = 0;
		IoSetCompletionRoutine(pIrp,AppleKeyboardSetLedIndicatorsComplete,pBuffer,TRUE,TRUE,TRUE);

		statusRet = IoCallDriver(pDevExt->m_pLowerDevice,pIrp);
	}
	__finally
	{
		if(bCompleteRequest)
		{
			pIrp->IoStatus.Status = statusRet;
			IoCompleteRequest(pIrp,IO_NO_INCREMENT);

			if(pBuffer)
				ExFreePool(pBuffer);

			if(pMdl)
				IoFreeMdl(pMdl);
		}

		if(bReleaseRemoveLock)
			IoReleaseRemoveLock(&pDevExt->m_removeLock,pIrp);
	}
	return statusRet;
}

// set led complete
NTSTATUS AppleKeyboardSetLedIndicatorsComplete(__in PDEVICE_OBJECT pDeviceObject,__in PIRP pIrp,__in PVOID pContext)
{
	CDeviceExtension* pDevExt = static_cast<CDeviceExtension*>(pDeviceObject->DeviceExtension);

	if(pIrp->PendingReturned)
		IoMarkIrpPending(pIrp);

	IoFreeMdl(pIrp->MdlAddress);
	pIrp->MdlAddress = static_cast<PMDL>(IoGetCurrentIrpStackLocation(pIrp)->Parameters.Others.Argument4);
	ExFreePool(pContext);

	IoReleaseRemoveLock(&pDevExt->m_removeLock,pIrp);

	return STATUS_SUCCESS;
}